package com.rtsapp.version.builder.tool;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.rtsapp.version.builder.entity.Resource;
import com.rtsapp.version.builder.entity.VersionInfo;
import com.rtsapp.version.builder.utils.ConsoleProgressBar;
import com.rtsapp.version.builder.utils.FileUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by admin on 15-10-24.
 */
public class VersionTool {
    private static final Set<String> IGNORE_FILE_NAMES = new HashSet<>();

    private static final JsonObject VERSION_INFO = new JsonObject();
    private static final Scanner input = new Scanner(System.in);
    private static Element root;
    private static boolean arg_onlyLocal = false;//仅本地生成,不上传到cdn和版本服务器
    private static boolean arg_onlyUpload = false;//仅本地生成和上传到cdn,不上传版本服务器
    private static boolean arg_onlyUpdate = false;//仅本地生成和上传版本服务器,不上传到cdn
    private static boolean arg_onlySave = false;//仅将要上传的zip文件保存在version_file目录,不删除,也不上传
    private static boolean arg_q = false;//静默模式，用执行jar传入的参数运行
    private static boolean arg_f = false;//强行模式，不强制要求确认输入消息是否有误

    private static String username = "";
    private static String password = "";

    static {
        IGNORE_FILE_NAMES.add(".DS_Store");
        IGNORE_FILE_NAMES.add(".git");

        try {
            SAXReader saxReader = new SAXReader();
            Document dom = saxReader.read(new File("cfg/config.xml"));
            root = dom.getRootElement();
        } catch (DocumentException e) {
            printErrAndExit(e);
        }

    }

    public static void main(String[] args) throws IOException, InterruptedException {

        String platform = null;
        Boolean debug = null;
        Boolean milestone = null;

        for (String arg : args) {
            if (arg.equalsIgnoreCase("--onlyLocal")) {
                arg_onlyLocal = true;
            }
            if (arg.equalsIgnoreCase("--onlyUpload")) {
                arg_onlyUpload = true;
            }
            if (arg.equalsIgnoreCase("--onlyUpdate")) {
                arg_onlyUpdate = true;
            }
            if (arg.equalsIgnoreCase("--onlySave")) {
                arg_onlySave = true;
                arg_onlyLocal = true;
            }
            if (arg.toLowerCase().startsWith("--username=")) {
                username = arg.split("=")[1];
            }
            if (arg.toLowerCase().startsWith("--password=")) {
                password = arg.split("=")[1];
            }
            if (arg.toLowerCase().startsWith("-q")) {
                arg_q = true;
            }
            if (arg.toLowerCase().startsWith("-f")) {
                arg_f = true;
            }
            if (arg.toLowerCase().startsWith("-qf")) {
                arg_q = true;
                arg_f = true;
            }
            if (arg.toLowerCase().startsWith("-fq")) {
                arg_f = true;
                arg_q = true;
            }
            if (arg.toLowerCase().startsWith("--platform=")) {
                platform = arg.split("=")[1];
            }
            if (arg.toLowerCase().startsWith("--milestone=")) {
                milestone = Boolean.parseBoolean(arg.split("=")[1].toLowerCase());
            }
        }
        if (arg_q) {
            if (platform == null) {
                printErrAndExit("platform 不正确");
            }
            if (milestone == null) {
                printErrAndExit("milestone 不正确");
            }
            debug = true;
        }
        if (arg_onlySave && arg_onlyUpload) {
            printErrAndExit("You can not select '--onlySaveZip' and '--arg_onlyUpload'");
        }
        if (arg_onlyLocal && arg_onlyUpload) {
            printErrAndExit("You can not select '--arg_onlyLocal' and '--arg_onlyUpload'");
        }
        //        boolean hasArgs = false;
        //        if (args.length != 0) {
        //            if (args.length == 34) {
        //                hasArgs = true;
        //            } else {
        //                printErrAndExit("has args, but length not equals 3");
        //            }
        //        }

        if (!arg_q) {
            System.out.println("请输入平台(IOS, Android, OneStore):");
            platform = input.nextLine();
        }
        if (platform.equals("IOS")) {
            VERSION_INFO.addProperty("platform", 1);
        } else if (platform.equals("Android")) {
            VERSION_INFO.addProperty("platform", 2);
        } else if (platform.equals("OneStore")) {
            VERSION_INFO.addProperty("platform", 3);
        }

        if (!arg_q) {
            System.out.println("是否为测试版(发布新版本时为true, 将已发布的新版本开放成正式版为false):");
            debug = Boolean.valueOf(input.nextLine().trim());
        }
        VERSION_INFO.addProperty("isDebug", debug);
        if (!debug) {
            if (arg_onlyLocal || arg_onlyUpload) {
                printErrAndExit("只有生成调试版才可以使用参数--onlyLocal或者--arg_onlyUpload");
                return;
            }
            updateGM();
            return;
        }

        Map<String, String> channelMap = new HashMap<>();
        File channelSiteFile = new File("data/channelSites.txt");
        if (channelSiteFile.exists()) {
            try (BufferedReader reader = new BufferedReader(new FileReader(channelSiteFile))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    String[] channelSiteStrs = line.split("->");
                    if (channelSiteStrs.length == 2) {
                        channelMap.put(channelSiteStrs[0].trim().toUpperCase(), channelSiteStrs[1].trim());
                    }
                }
            }
        }

        if (!arg_q) {
            System.out.println("是否为里程碑版本(是true, 否false):");
            milestone = Boolean.valueOf(input.nextLine().trim());
        }
        JsonObject channelDownloadSites = null;
        List<String> channel = new ArrayList<>();
        if (milestone) {

            String channelsStr;
            if (!arg_q) {
                System.out.print("请输入渠道(");
            }
            String names = "";
            for (String name : channelMap.keySet()) {
                names += name;
                names += ", ";
            }
            if (!arg_q) {
                System.out.println(names + "或者[其他渠道号], 并用逗号分割):");
                channelsStr = input.nextLine();
            } else {
                channelsStr = names;
            }
            channelsStr = channelsStr.replace("，", ",");
            channelsStr = channelsStr.trim();
            if (channelsStr.endsWith(",")) {
                channelsStr.substring(0, channelsStr.length() - 1);
            }
            String[] tmpChannels = channelsStr.split(",");
            int size = 0;
            if (tmpChannels.length > 1 || tmpChannels[0].trim().length() > 0) {
                size = tmpChannels.length;
            }
            String[] channels = new String[size + 1];
            channels[0] = "DEFAULT";
            if (channels.length > 1) {
                for (int i = 0; i < tmpChannels.length; i++) {
                    channels[i + 1] = tmpChannels[i].trim();
                }
            }
            channelDownloadSites = new JsonObject();
            for (String channelStr : channels) {
                String tmpChannelStr = channelStr.trim().toUpperCase();
                if (!arg_q) {
                    System.out.println("请输入[" + channelStr.trim().toUpperCase() + "]里程碑版本下载链接(http开头):");
                }
                String defaultUrl = channelMap.get(tmpChannelStr);
                boolean hasDefaultValue = false;
                if (defaultUrl != null) {
                    if (!arg_q) {
                        System.out.println("如果输入[true]则使用默认值:\"" + defaultUrl + "\"");
                    }
                    hasDefaultValue = true;
                }
                String downloadSite;
                if (!arg_q) {
                    downloadSite = input.nextLine();
                } else {
                    downloadSite = "true";
                }
                if (downloadSite.trim().equalsIgnoreCase("true")) {
                    if (!hasDefaultValue) {
                        printErrAndExit("[" + channelStr + "]渠道没有默认下载链接, 输入错误.");
                        return;
                    } else {
                        downloadSite = defaultUrl;
                    }
                } else {
                    if (!downloadSite.startsWith("http")) {
                        printErrAndExit("里程碑地址不正确, 应以http开头");
                        return;
                    }
                    channelMap.put(tmpChannelStr, downloadSite);
                }
                channelDownloadSites.addProperty(tmpChannelStr, downloadSite);
                channel.add(tmpChannelStr);
            }

            File versionFile = new File("data/channelSites.txt");
            if (!versionFile.getParentFile().exists()) {
                versionFile.getParentFile().mkdir();
            }
            versionFile.createNewFile();
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(versionFile, false))) {
                for (Map.Entry<String, String> channelSiteItem : channelMap.entrySet()) {
                    writer.write(channelSiteItem.getKey() + "->" + channelSiteItem.getValue());
                    writer.newLine();
                }
                writer.flush();
            }


        }
        List<VersionInfo> versionList = new ArrayList<>();
        int maxVersionNo = getAllVersionInfo(platform, versionList, milestone);

        if (versionList.size() == 0) {
            printErrAndExit("unknown error");
            return;
        }

        System.out.println("\n");
        System.out.println(platform + " 平台");
        System.out.println((debug ? "是" : "不是") + " 测试版");
        System.out.println((channel.size() > 0 ? "是" : "不是") + " 里程碑版本");
        if (channel.size() > 0) {
            System.out.println("里程碑版本下载链接: ");
            for (String channelName : channel) {
                System.out.println("\t" + channelName + " -> " + channelMap.get(channelName));
            }
        }
        System.out.println("目标 CDN -> " + root.element("cdn").attributeValue("type"));
        System.out.println("\n");
        if (!arg_f) {
            System.out.println("确认以上信息正确无误(是的,没问题true, 填错了,不正确false):");
            if (!Boolean.valueOf(input.nextLine().trim())) {
                printErrAndExit("用户取消操作");
                return;
            }
        } else {
            Thread.sleep(3000);
        }

        VersionInfo milestoneVersion = versionList.get(0);

        JsonObject pkgVersion = new JsonObject();
        VERSION_INFO.add("pkgVersion", pkgVersion);

        VersionInfo currentVersion = versionList.get(versionList.size() - 1);

        FileUtils.delFolder(platform + "/version_dir_" + maxVersionNo + "/upload");
//        FileUtils.delFolder(platform + "/version_dir_" + maxVersionNo + "/zip");

        VERSION_INFO.addProperty("maxVersionNo", currentVersion.versionNo);

        Element cdn = root.element("cdn");

        VERSION_INFO.addProperty("urlPrefix", cdn.attributeValue("downloadHost") + "/" + root.element("channel").attributeValue("value") + "/${platform}/version_${maxVersionNo}/zip/upload_${maxVersionNo}_${versionNo}/");
        JsonArray versions = new JsonArray();
        VERSION_INFO.add("versions", versions);

        if (channel.size() > 0) {

            pkgVersion.addProperty("downloadSite", "");//废弃字段

            pkgVersion.add("channelDownloadSites", channelDownloadSites);
            pkgVersion.addProperty("versionNo", currentVersion.versionNo);
            saveVersionInfo(platform, currentVersion, currentVersion.resourceMap, null, maxVersionNo);
            if (!arg_onlyLocal && !arg_onlyUpload) {
                updateGM();
            }

//            FileUtils.delFolder(platform + "/version_dir_" + maxVersionNo + "/upload");
//            FileUtils.delFolder(platform + "/version_dir_" + maxVersionNo + "/zip");

//            FileUtils.delFolder(platform + "/version_files");
//            FileUtils.copyFolder(platform + "/version_dir_" + maxVersionNo + "/files", platform + "/version_files");

            FileUtils.delFolder(platform + "/version_dir_" + maxVersionNo);
            return;
        }

        if (versionList.size() == 1) {
            System.err.println("只有最新版是有效版本,而且它不是基础版");
            System.exit(1);
            return;
        }

        pkgVersion.addProperty("versionNo", milestoneVersion.versionNo);
        pkgVersion.addProperty("downloadSite", milestoneVersion.downloadSite);

        Map<String, Resource> toLastVersionResMap = null;
        Map<String, Resource> needZipResMap = new HashMap<>();

        Map<String, Resource> allNeedZipResMap = new HashMap<>();
//
//        for (Resource curVersionRes : currentVersion.resourceMap.values()) {
//            allNeedZipResMap.putIfAbsent(curVersionRes.path, curVersionRes);
//        }
//
        for (int i = versionList.size() - 2; i >= 0; i--) {
            needZipResMap.clear();

            VersionInfo versionInfo = versionList.get(i);

            Map<String, Resource> addedsAppResMap = new HashMap<>();
            Map<String, Resource> changedsAppResMap = new HashMap<>();
            Map<String, Resource> deletedsAppResMap = new HashMap<>();

            calculateVersionDiff(versionInfo, currentVersion, addedsAppResMap, changedsAppResMap, deletedsAppResMap);

            needZipResMap.putAll(addedsAppResMap);
            needZipResMap.putAll(changedsAppResMap);

            for (Resource resource : addedsAppResMap.values()) {
                allNeedZipResMap.putIfAbsent(resource.path, resource);
            }
            for (Resource resource : changedsAppResMap.values()) {
                allNeedZipResMap.putIfAbsent(resource.path, resource);
            }


            if (toLastVersionResMap == null) {
                toLastVersionResMap = new HashMap<>();
                toLastVersionResMap.putAll(needZipResMap);

//                System.out.println("相对于基础包被改变的文件数量:\t" + needZipResMap.size());
                System.out.println("相对于上个版本被改变的文件数量:\t" + toLastVersionResMap.size());
                System.out.println("\n");
                if (!arg_f) {
                    System.out.println("确认以上信息正确无误(是的,没问题true, 这不是我期望的,不正确false):");
                    if (!Boolean.valueOf(input.nextLine().trim())) {
                        printErrAndExit("用户取消操作");
                        return;
                    }
                } else {
                    Thread.sleep(3000);
                }

            }

            JsonArray files = unpack(platform, platform + "/version_dir_" + maxVersionNo, needZipResMap, versionInfo.versionNo, maxVersionNo, versionInfo);

            JsonObject versionJson = new JsonObject();
            versions.add(versionJson);
            versionJson.addProperty("versionNo", versionInfo.versionNo);
            versionJson.addProperty("md5", versionInfo.md5);
            versionJson.addProperty("size", versionInfo.size);
            versionJson.addProperty("partSize", Double.valueOf(Math.ceil(versionInfo.size / (Integer.parseInt(root.element("split_size").attributeValue("value")) * 1024.0))).intValue());
            versionJson.add("files", files);
        }

        VersionInfo versionInfo = new VersionInfo();

        JsonArray files = unpack(platform, platform + "/version_dir_" + maxVersionNo, allNeedZipResMap, 0, maxVersionNo, versionInfo);

        int partLength = Integer.parseInt(root.element("split_size").attributeValue("value"));

        VERSION_INFO.addProperty("partLength", partLength);

        JsonObject versionJson = new JsonObject();
        versions.add(versionJson);
        versionJson.addProperty("versionNo", versionInfo.versionNo);
        versionJson.addProperty("md5", versionInfo.md5);
        versionJson.addProperty("size", versionInfo.size);
        versionJson.addProperty("partSize", Double.valueOf(Math.ceil(versionInfo.size / (partLength * 1024.0))).intValue())
        ;
        versionJson.add("files", files);

        saveAppVersionDiffForUpload(platform, currentVersion, milestoneVersion, maxVersionNo);
        saveVersionInfo(platform, currentVersion, needZipResMap, toLastVersionResMap, maxVersionNo);

        String versionInfoStr = updateGM();

//        FileUtils.delFolder(platform + "/version_files" + "/upload");

        if (arg_onlySave) {
            try {
                File versionFile = new File("version.txt");
                versionFile.createNewFile();
                FileWriter fileWritter = new FileWriter(versionFile.getName(), true);
                BufferedWriter bufferWritter = new BufferedWriter(fileWritter);
                bufferWritter.write(versionInfoStr);
                bufferWritter.close();
            } catch (IOException e) {
                printErrAndExit(e);
            }

//            FileUtils.copyFolder(platform + "/version_files" + "/zip", platform + "/version_zip");

        }

        FileUtils.delFolder(platform + "/version_dir_" + maxVersionNo);

    }

    /***
     * 返回最近一个里程碑版本到最新版本的所有信息
     *
     * @param platform    平台
     * @param versionList 如果size==0,不可能,肯定是出错了
     *                    如果size==1,当前准备生成的最新版本是唯一一个版本,则应该为里程碑版本
     *                    如果size>1,[0]是最近一个里程碑版本,[size-1]是准备生成的版本
     * @return 当前, 即最大版本号
     * @throws FileNotFoundException 两种可能
     *                               1.平台目录不存在,这绝对是个人为操作引起的错误,连平台目录都没有,怎么把版本文件复制进去的
     *                               2.之前的某个旧版本日志文件不存在,估计是被误删了,撤销回来就可以了
     *                               3.正常情况下只有两种可能,如果你真的出现第三种可能了,估计是bug
     */
    private static int getAllVersionInfo(String platform, List<VersionInfo> versionList, boolean milestone) throws IOException {

        Map<Integer, File> versionDirMap = new HashMap<>();
        List<Integer> versionNoList = getAllVersionNo(platform, versionDirMap);

        System.out.println("\n开始载入找到的" + versionNoList.size() + "个版本\n");
        int maxVersionNo = 0;

        int maxVersionCount = Integer.parseInt(root.element("earliest_version").attributeValue("count"));
        for (int i = 0, j = 0; i < versionNoList.size() && (j < maxVersionCount || !versionList.get(versionList.size() - 1).milestone); i++, j++) {
            Integer versionNo = versionNoList.get(i);
            File versionDir = versionDirMap.get(versionNo);
            VersionInfo versionInfo;
            if (i == 0) {
                versionInfo = analysisVersionInfoFromDir(platform + "/version_files".replace("\\", "/"));
                versionInfo.platform = platform;
                versionInfo.versionNo = versionNo;
                versionInfo.buildTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
                versionInfo.milestone = milestone;
                maxVersionNo = versionNo;
            } else {
                versionInfo = analysisVersionInfoFromLog(versionDir.getPath().replace("\\", "/"));
                if (versionInfo == null) {
                    continue;
                }
                if (versionInfo.invalid || (!versionInfo.milestone && j >= maxVersionCount - 1)) {
                    j--;
                    continue;
                }
            }
            versionList.add(versionInfo);
            System.out.println("载入版本 " + versionInfo.versionNo);
            if (versionInfo.milestone) {
                break;
            }
        }

        Collections.reverse(versionList);
        return maxVersionNo;
    }

    private static List<Integer> getAllVersionNo(String platform, Map<Integer, File> versionDirMap) throws IOException {
        File platformDir = new File(platform);
        if (!platformDir.exists()) {
            throw new FileNotFoundException("平台目录不存在, " + platformDir.getAbsolutePath().replace("\\", "/"));
        }
        System.out.println("扫描 " + platformDir.getAbsolutePath());
        File[] versionTxts = platformDir.listFiles();
        System.out.println("扫描出" + versionTxts.length + "个版本记录文件");
        List<Integer> versionNoList = new ArrayList<>();
        String prefix = "version_";
        String suffix = ".txt";
//        String version_dir_prefix = "version_dir_";

        boolean isDir;

        for (File versionDir : versionTxts) {
            isDir = false;
            String fileName = versionDir.getName();
            if (fileName.contains(".DS_Store")) {
                continue;
            }
            if (!fileName.startsWith(prefix) || !fileName.endsWith(suffix)) {
//                if (!fileName.startsWith(version_dir_prefix)) {
//                    continue;
//                }
                isDir = true;
            }
            String versionNoStr;
            if (!isDir) {
                versionNoStr = fileName.substring(prefix.length(), fileName.length() - suffix.length());
            } else {
                continue;
//                versionNoStr = fileName.substring(version_dir_prefix.length());
            }
            int versionNo = 0;
            try {
                versionNo = Integer.parseInt(versionNoStr);
            } catch (NumberFormatException e) {
                printErrAndExit("file name has an error: " + platform + "/" + fileName, e);
            }
            versionNoList.add(versionNo);
            if (versionDirMap != null) {
                versionDirMap.put(versionNo, versionDir);
            }
        }
        Collections.sort(versionNoList, Collections.reverseOrder());

        if (versionNoList.size() == 0) {
            throw new FileNotFoundException("新版本目录和文件不存在,版本文件数量为0");
        }
        File file = new File(platform + "/" + prefix + versionNoList.get(0) + suffix);
        if (!file.exists()) {
            throw new FileNotFoundException("新版本文件不存在");
        }
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String line;
            if ((line = reader.readLine()) != null && line.startsWith("#")) {
                throw new FileNotFoundException("新版本目录和文件不存在, " + platform + "/" + prefix + versionNoList.get(0) + suffix);
            }
        }
        return versionNoList;
    }

    /**
     * 读取一个版本文件夹的文件内容，计算该版本的所有文件信息
     *
     * @param dirPath 版本文件夹路径
     * @return {@code null}  如果这个配置文件不存在
     */
    private static VersionInfo analysisVersionInfoFromDir(String dirPath) throws FileNotFoundException {
        if (!dirPath.endsWith("/")) {
            dirPath += "/";
        }
        File versionDir = new File(dirPath);
        if (!versionDir.exists()) {
            throw new FileNotFoundException("文件目录结构不正确,没有version_files文件夹");
        }
        VersionInfo version = new VersionInfo();

        List<File> files = new ArrayList<>();
        FileUtils.scannerAllFiles(versionDir, files, IGNORE_FILE_NAMES);

        for (File file : files) {
            Resource resource = new Resource();
            try (FileInputStream inputStream = new FileInputStream(file)) {
                resource.md5 = FileUtils.getMd5(inputStream, file.length());
                resource.size = inputStream.available();
            } catch (IOException e) {
                e.printStackTrace();
            }
            resource.path = file.getPath().replace("\\", "/").replace(dirPath, "");
            version.resourceMap.put(resource.path, resource);
        }
        return version;
    }

    /**
     * 读取一个日志文件文件内容，分析该版本的所有文件信息
     *
     * @param logPath 日志文件路径
     * @return {@code null}  如果这个配置文件不存在
     */
    private static VersionInfo analysisVersionInfoFromLog(String logPath) throws FileNotFoundException {
        File versionLog = new File(logPath);
        if (!versionLog.exists()) {
            throw new FileNotFoundException();
        }
//        if (versionLog.isDirectory()) {
//            throw new FileNotFoundException("version_dir_*文件夹不是最新版本");
//        }
        try (BufferedReader reader = new BufferedReader(new FileReader(versionLog))) {
            VersionInfo version = new VersionInfo();
            String line;

            Boolean old = null;
            Boolean pkg = null;
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("===")) {
                    break;//资源文件分隔符,表示版本文件结束,以下内容为版本资源文件信息
                }
                if (line.startsWith("#")) {
                    continue;//注释
                }
                String[] atts = line.split("=");
                if (atts.length == 2) {
                    switch (atts[0].trim()) {
                        case "Platform":
                            version.platform = atts[1];
                            break;
                        case "VersionNo":
                            version.versionNo = Integer.parseInt(atts[1]);
                            break;
                        case "Description":
                            version.description = atts[1];
                            break;
                        case "BuildTime":
                            version.buildTime = atts[1];
                            long earliestDays = Long.parseLong(root.element("earliest_version").attributeValue("days"));
                            if (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(version.buildTime).getTime() < System.currentTimeMillis() - earliestDays * 24 * 60 * 60 * 1000) {
                                if (pkg != null && !pkg) {
                                    return null;
                                }
                                old = true;
                            }
                            break;
                        case "Milestone":
                            version.milestone = Boolean.parseBoolean(atts[1]);
                            if (!version.milestone) {
                                if (old != null && old) {
                                    return null;
                                }
                                pkg = false;
                            }
                            break;
                        case "DownloadSite":
                            version.downloadSite = atts[1];
                            break;
                        case "Invalid":
                            version.invalid = Boolean.parseBoolean(atts[1]);
                            break;
                    }
                }
            }
            while ((line = reader.readLine()) != null) {
                Resource resource = new Resource();
                do {
                    if (line.startsWith("===")) {
                        break;//资源文件分隔符,表示版本文件结束,以下内容为版本资源文件信息
                    }
                    if (line.startsWith("#")) {
                        continue;//注释
                    }
                    String[] atts = line.split("=");
                    if (atts.length == 2) {
                        switch (atts[0].trim()) {
                            case "Path":
                                resource.path = atts[1];
                                break;
                            case "Md5":
                                resource.md5 = atts[1];
                                break;
                            case "Size":
                                resource.size = Integer.parseInt(atts[1]);
                                break;
                        }
                    }
                } while ((line = reader.readLine()) != null);
                if (resource.size > 0) {
                    version.resourceMap.put(resource.path, resource);
                }
            }
            return version;
        } catch (Throwable e) {
            printErrAndExit(e);
        }
        return null;
    }

    private static void printErrAndExit(String errMsg, Throwable e) {
        System.err.println(errMsg);
        e.printStackTrace();
        System.exit(1);
    }

    private static void printErrAndExit(String errMsg) {
        System.err.println(errMsg);
        System.exit(1);
    }

    private static void printErrAndExit(Throwable e) {
        e.printStackTrace();
        System.exit(1);
    }

    /**
     * 保存版本信息
     *
     * @param platform     平台
     * @param versionInfo  版本信息
     * @param curVersionNo 当前版本号
     */
    private static void saveVersionInfo(String platform, VersionInfo versionInfo, Map<String, Resource> changedResourceMap, Map<String, Resource> toLastVersionResourceMap, int curVersionNo) throws IOException {

        FileUtils.delFolder(platform + "/version_dir_" + curVersionNo + "/upload");

        File versionFile = new File(platform + "/version_" + curVersionNo + ".txt");
        versionFile.createNewFile();
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(versionFile, false))) {
            writer.write("#平台");
            writer.newLine();
            writer.write("Platform=" + platform);
            writer.newLine();
            writer.write("#版本号");
            writer.newLine();
            writer.write("VersionNo=" + versionInfo.versionNo);
            writer.newLine();
            writer.write("#描述");
            writer.newLine();
            writer.write("Description=" + versionInfo.description);
            writer.newLine();
            writer.write("#时间");
            writer.newLine();
            writer.write("BuildTime=" + versionInfo.buildTime);
            writer.newLine();
            writer.write("#是否整包更新");
            writer.newLine();
            writer.write("Milestone=" + versionInfo.milestone);
            writer.newLine();
            writer.write("#整包更新路径");
            writer.newLine();
            writer.write("DownloadSite=" + versionInfo.downloadSite);
            writer.newLine();
            writer.write("#是否被废弃");
            writer.newLine();
            writer.write("Invalid=" + versionInfo.invalid);
            writer.newLine();
            writer.write("#相对于基础包被改变的文件数量");
            writer.newLine();
            writer.write("ChangedFiles=" + changedResourceMap.size());
            writer.newLine();

            if (toLastVersionResourceMap != null) {

                writer.write("#相对于上个版本被改变的文件数量");
                writer.newLine();
                writer.write("ChangedFiles=" + toLastVersionResourceMap.size());
                writer.newLine();
                writer.write("#相对于上个版本被改变的文件信息");
                writer.newLine();

                writer.write("------------------------------------------------");
                writer.newLine();

                for (Map.Entry<String, Resource> appRes : toLastVersionResourceMap.entrySet()) {
                    writer.write("#文件路径");
                    writer.newLine();
                    writer.write("Path=" + appRes.getKey());
                    writer.newLine();
                    writer.write("#MD5值");
                    writer.newLine();
                    writer.write("Md5=" + appRes.getValue().md5);
                    writer.newLine();
                    writer.write("#大小");
                    writer.newLine();
                    writer.write("Size=" + appRes.getValue().size);
                    writer.newLine();

                }

                writer.write("------------------------------------------------");
                writer.newLine();

            }

            writer.write("#所有文件信息");
            writer.newLine();

            writer.write("================================================");
            writer.newLine();

            for (Map.Entry<String, Resource> appRes : versionInfo.resourceMap.entrySet()) {
                File fromFile = new File(platform + "/version_files/" + appRes.getKey());
                File toFile = new File(platform + "/version_dir_" + curVersionNo + "/upload/" + appRes.getKey());
                if (!toFile.getParentFile().exists()) {
                    toFile.getParentFile().mkdirs();
                }
                FileUtils.copyFile(fromFile, toFile);
                writer.write("#文件路径");
                writer.newLine();
                writer.write("Path=" + appRes.getKey());
                writer.newLine();
                writer.write("#MD5值");
                writer.newLine();
                writer.write("Md5=" + appRes.getValue().md5);
                writer.newLine();
                writer.write("#大小");
                writer.newLine();
                writer.write("Size=" + appRes.getValue().size);
                writer.newLine();
                writer.write("================================================");
                writer.newLine();

            }

//            writer.write("#");
//            writer.write(VERSION_INFO.toString());
//            writer.newLine();
//            String urlPrefix;
//            JsonArray jsonArray = VERSION_INFO.get("versions").getAsJsonArray();
//
//            String platformStr = "";
//
//            switch (VERSION_INFO.get("platform").getAsInt()) {
//                case 1:
//                    platformStr = "IOS";
//                    break;
//                case 2:
//                    platformStr = "Android";
//                    break;
//            }
//
//            for (int i = 0; i < jsonArray.size(); i++) {
//
//                JsonObject jsonObject = jsonArray.get(i).getAsJsonObject();
//
//                String maxVersionNo = String.valueOf(VERSION_INFO.get("maxVersionNo").getAsString());
//                String versionNo = String.valueOf(jsonObject.get("versionNo").getAsInt());
//
//                urlPrefix = VERSION_INFO.get("urlPrefix").getAsString()
//                        .replace("${platform}", platformStr)
//                        .replace("${maxVersionNo}", maxVersionNo)
//                        .replace("${versionNo}", versionNo);
//
//                int partSize = jsonObject.get("partSize").getAsInt();
//                for (int j = 0; j < partSize; j++) {
//                    writer.write("#" + urlPrefix + "upload_" + maxVersionNo + "_" + versionNo + ".zip." + String.format("%0" + String.valueOf(partSize - 1).length() + "d", i) + ".part");
//                    writer.newLine();
//                }
//
//            }
            writer.flush();
        }
    }

    /**
     * 保存需要
     *
     * @param platform     平台
     * @param newVersion   新版本
     * @param curVersionNo 当前版本
     */
    private static void saveAppVersionDiffForUpload(String platform, VersionInfo newVersion, VersionInfo oldVersion, int curVersionNo) {


        Map<String, Resource> resourceMap = new HashMap<>();

        Map<String, Resource> addedsAppResMap = new HashMap<>();
        Map<String, Resource> changedsAppResMap = new HashMap<>();
        Map<String, Resource> deletedsAppResMap = new HashMap<>();

        if (oldVersion == null) {
            resourceMap.putAll(newVersion.resourceMap);
        } else {

            calculateVersionDiff(oldVersion, newVersion, addedsAppResMap, changedsAppResMap, deletedsAppResMap);

            resourceMap.putAll(addedsAppResMap);
            resourceMap.putAll(changedsAppResMap);
        }

        for (Map.Entry<String, Resource> appRes : resourceMap.entrySet()) {
            File fromFile = new File(platform + "/version_files/" + appRes.getKey());
            if (!fromFile.exists()) {//不在新目录中,相对基础包删除的文件
                continue;
            }
            File toFile = new File(platform + "/version_dir_" + curVersionNo + "/upload/" + appRes.getKey());
            if (!toFile.getParentFile().exists()) {
                toFile.getParentFile().mkdirs();
            }
            FileUtils.copyFile(fromFile, toFile);
        }
    }


    /**
     * 计算版本差异
     *
     * @param baseVersion       基础版本库
     * @param curFullVersion    需要上传的版本库
     * @param addedsAppResMap   增加的文件列表
     * @param changedsAppResMap 改变的文件列表
     * @param deletedsAppResMap 删除的文件列表
     */
    private static void calculateVersionDiff(VersionInfo baseVersion, VersionInfo curFullVersion, Map<String, Resource> addedsAppResMap, Map<String, Resource> changedsAppResMap, Map<String, Resource> deletedsAppResMap) {

        addedsAppResMap.clear();
        changedsAppResMap.clear();
        deletedsAppResMap.clear();

        for (Resource curVersionRes : curFullVersion.resourceMap.values()) {
            Resource baseVersionRes = baseVersion.resourceMap.get(curVersionRes.path);
            if (baseVersionRes == null) {
                addedsAppResMap.put(curVersionRes.path, curVersionRes);
                continue;
            }
            if (!baseVersionRes.md5.equals(curVersionRes.md5)) {
                changedsAppResMap.put(curVersionRes.path, curVersionRes);
                continue;
            }
        }

        for (Resource baseVersionRes : baseVersion.resourceMap.values()) {
            Resource curVersionRes = curFullVersion.resourceMap.get(baseVersionRes.path);
            if (curVersionRes == null) {
                deletedsAppResMap.put(baseVersionRes.path, baseVersionRes);
                continue;
            }
        }
    }


    private static JsonArray unpack(String platform, String path, Map<String, Resource> resourceMap, int baseVersion, int maxVersionNo, VersionInfo versionInfo) {

        String targetFileName = path + "/zip/upload_" + maxVersionNo + "_" + baseVersion + "/upload_" + maxVersionNo + "_" + baseVersion + ".zip";
        File targetFile = new File(targetFileName);
        boolean needZip = true;
        if (targetFile.exists()) {
            System.out.println("发现已存在的upload_" + maxVersionNo + "_" + baseVersion + "/upload_" + maxVersionNo + "_" + baseVersion + ".zip文件, 跳过打包, 直接切割!");
            needZip = false;
//            needZip = !Boolean.valueOf(input.nextLine().trim());
//            if (needZip) {
//                printErrAndExit("用户手动停止");
//                return null;
//            }
        }
        if (needZip) {
            File zipVersionFolder = new File(path + "/zip/upload_" + maxVersionNo + "_" + baseVersion);
            if (!zipVersionFolder.exists()) {

                for (Map.Entry<String, Resource> appRes : resourceMap.entrySet()) {
                    File fromFile = new File(platform + "/version_files/" + appRes.getKey());
                    if (!fromFile.exists()) {//不在新目录中,相对基础包删除的文件
                        continue;
                    }
                    File toFile = new File(path + "/upload/" + appRes.getKey());
                    if (!toFile.getParentFile().exists()) {
                        toFile.getParentFile().mkdirs();
                    }
                    FileUtils.copyFile(fromFile, toFile);
                }
                File zipFolder = new File(path + "/zip");
                if (!zipFolder.exists()) {
                    zipFolder.mkdirs();
                }
            }

            if (new File(path + "/upload").exists()) {
                FileUtils.zipCompression(path + "/upload", targetFileName);
            }

        }
        try (FileInputStream inputStream = new FileInputStream(targetFile)) {
            versionInfo.md5 = FileUtils.getMd5(inputStream, targetFile.length());
            versionInfo.size = inputStream.available();
        } catch (IOException e) {
            e.printStackTrace();
        }
        List<String> filePaths = null;
        JsonArray files = new JsonArray();
        if (new File(targetFileName).exists()) {
            try {
                filePaths = FileUtils.splitBySize(path + "/zip/upload_" + maxVersionNo + "_" + baseVersion + "/", targetFileName, Integer.parseInt(root.element("split_size").attributeValue("value")) * 1024, files);
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
            if (filePaths != null) {
                List<String> tmpFilePath = new ArrayList<>();
                String uneed = platform + "/version_dir_" + maxVersionNo + "/";
                for (String filePath : filePaths) {
                    tmpFilePath.add(filePath.substring(uneed.length()));
                }
                upload(platform, maxVersionNo, tmpFilePath);
            }
        }

        FileUtils.delFolder(path + "/upload");

        return files;
    }


    private static void upload(String platform, int versionNo, List<String> fileNames) {
        if (arg_onlyLocal) {
            System.out.print("--onlyLocal模式, 跳过上传");
            return;
        }
        if (arg_onlyUpdate) {
            System.out.print("--onlyUpdate模式, 跳过上传");
            return;
        }
        try {

            String channel = root.element("channel").attributeValue("value");

            String keyPrefix = channel + "/" + platform + "/version_" + versionNo + "/";
            String pathPrefix = platform + "/version_dir_" + versionNo + "/";
            String cdnType = root.element("cdn").attributeValue("type");
            System.out.println("CDN 类型为: " + cdnType);

            int count = fileNames.size();

            int poolSize = count;
            if (poolSize > 5) {
                poolSize = 5;
            }

            ThreadPoolExecutor threadPool = new ThreadPoolExecutor(poolSize, poolSize, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(count + 1));

            Times times = new Times();
            ConsoleProgressBar cpb = new ConsoleProgressBar(0, count, 50, '=');
            cpb.show("开始上传: " + pathPrefix, 0);

            for (int i = 0; i < count; i++) {

                threadPool.execute(new FileUtils.PutFileRunnable(cdnType, keyPrefix, pathPrefix, fileNames.get(i), times, cpb));

            }

            threadPool.shutdown();//只是不能再提交新任务，等待执行的任务不受影响

            try {
                boolean loop;
                do {    //等待所有任务完成
                    loop = !threadPool.awaitTermination(2, TimeUnit.SECONDS);  //阻塞，直到线程池里所有任务结束
                } while (loop);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            int num = times.times.get() - count;
            if (num > 0) {
                printErrAndExit(num + " 个文件上传失败");
                return;
            }

        } catch (Throwable e) {
            printErrAndExit(e);
        }

//        FileUtils.delFolder(platform + "/version_" + versionNo + "/upload");

    }

    private static String updateGM() {

        String versionInfo = VERSION_INFO.toString();

        if (arg_onlyLocal) {
            System.out.print("--onlyLocal模式, 跳过提交");
            return versionInfo;
        }
        if (arg_onlyUpload) {
            System.out.print("--onlyUpload模式, 跳过提交");
            return versionInfo;
        }

        Element serverHost = root.element("serverHost");
        Element user = root.element("user");

        if (username.length() == 0) {
            username = user.attributeValue("name");
        }
        if (password.length() == 0) {
            password = user.attributeValue("password");
        }

        String res = sendHttpPost(serverHost.attributeValue("value") + "/version/admin/genVersion", "u=" + username + "&p=" + password + "&version=" + versionInfo.replace("&", "^-^"));

        if (res != null && res.contains("{\"res\":\"success\"}")) {
            System.out.println(res);
            return versionInfo;
        }
        printErrAndExit(res);
        return versionInfo;
    }

    private static String sendHttpPost(String urlStr, String args) {
        try {
            /**
             * 首先要和URL下的URLConnection对话。 URLConnection可以很容易的从URL得到。比如： // Using
             *  java.net.URL and //java.net.URLConnection
             *
             *  使用页面发送请求的正常流程：在页面http://www.faircanton.com/message/loginlytebox.asp中输入用户名和密码，然后按登录，
             *  跳转到页面http://www.faircanton.com/message/check.asp进行验证
             *  验证的的结果返回到另一个页面
             *
             *  使用java程序发送请求的流程：使用URLConnection向http://www.faircanton.com/message/check.asp发送请求
             *  并传递两个参数：用户名和密码
             *  然后用程序获取验证结果
             */
            URL url = new URL(urlStr);
            URLConnection connection = url.openConnection();
            /**
             * 然后把连接设为输出模式。URLConnection通常作为输入来使用，比如下载一个Web页。
             * 通过把URLConnection设为输出，你可以把数据向你个Web页传送。下面是如何做：
             */
            connection.setDoOutput(true);
            /**
             * 最后，为了得到OutputStream，简单起见，把它约束在Writer并且放入POST信息中，例如： ...
             */
            OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "8859_1");
            out.write(args); //向页面传递数据。post的关键所在！
            // remember to clean up
            out.flush();
            out.close();
            /**
             * 这样就可以发送一个看起来象这样的POST：
             * POST /jobsearch/jobsearch.cgi HTTP 1.0 ACCEPT:
             * text/plain Content-type: application/x-www-form-urlencoded
             * Content-length: 99 username=bob password=someword
             */
            // 一旦发送成功，用以下方法就可以得到服务器的回应：
            String sCurrentLine;
            String sTotalString;
            sTotalString = "";
            InputStream l_urlStream;
            l_urlStream = connection.getInputStream();
            // 传说中的三层包装阿！
            BufferedReader l_reader = new BufferedReader(new InputStreamReader(l_urlStream));
            while ((sCurrentLine = l_reader.readLine()) != null) {
                sTotalString += sCurrentLine;
            }
            return sTotalString;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static class Times {
        public AtomicInteger times = new AtomicInteger();
    }


}
